#include "EventString.h"
#include <QDebug>
#include "Event.h"

EventString::EventString()
{
    ;
}

EventString::EventString(const QString &occur, const QString &format)
    : m_occur(occur), m_format(format)
{
    ;
}

//EventString::EventString(const EventString &event)
//{
//    m_occur = event.occur();
//    m_format = event.format();
//    m_sectionNodes = event.m_sectionNodes;
//    m_dateTime = event.m_dateTime;
//}

bool EventString::parseFormat(const QString &format)
{
    QVector<SectionNode> newSectionNodes;
    for (int i = 0; i < format.size(); ++i) {
        const char sect = format.at(i).toLatin1();
        switch (sect) {
        case 'h':
        case 'H': {
            const SECTION hour = (sect == 'h') ? HOUR12_SECTION : HOUR24_SECTION;
            const SectionNode sn = { hour, i, countRepeat(format, i, 2), 5 };
            newSectionNodes.append(sn);
            i += sn.count - 1;
            break;
        }
        case 'm': {
            const SectionNode sn = { MINUTE_SECTION, i, countRepeat(format, i, 2), 3 };
            newSectionNodes.append(sn);
            i += sn.count - 1;
            break;
        }
        case 's': {
            const SectionNode sn = { SECOND_SECTION, i, countRepeat(format, i, 2), 2 };
            newSectionNodes.append(sn);
            i += sn.count - 1;
            break;
        }
        case 'z': {
            const SectionNode sn = { MSECOND_SECTION, i, countRepeat(format, i, 3) < 3 ? 1 : 3, 1 };
            newSectionNodes.append(sn);
            i += sn.count - 1;
            break;
        }
        case 'A':
        case 'a':
        case 'P':
        case 'p': {
            const SECTION ampm = (sect == 'P' || sect == 'p') ? PM_SECTION : AM_SECTION;
            const SectionNode sn = { ampm, i, 0, 4 };
            newSectionNodes.append(sn);
            break;
        }
        case 'R':
        case 'r':
        case 'Y':
        case 'y': {
            const int repeat = countRepeat(format, i, 4);
            if (repeat >= 2) {
                SECTION year;
                if (sect == 'R' || sect == 'r') {
                    year = (repeat == 4) ? YEAR4_INFO_SECTION : YEAR2_INFO_SECTION;
                } else {
                    year = (repeat == 4) ? YEAR4_SECTION : YEAR2_SECTION;
                }
                const SectionNode node = { year, i, (repeat == 4) ? 4 : 2, 9 };
                newSectionNodes.append(node);
                i += node.count - 1;
            }
            break;
        }
        case 'M': {
            const SectionNode sn = { MONTH_SECTION, i, countRepeat(format, i, 2), 8 };
            newSectionNodes.append(sn);
            i += sn.count - 1;
            break;
        }
        case 'W':
        case 'w':
        case 'F':
        case 'f': {
            SECTION week = WEEK_SECTION;
            if (sect == 'F' || sect == 'f') {
                week = FULL_WEEK_SECTION;
            }
            const SectionNode sn = { week, i, countRepeat(format, i, 1), 7 };
            newSectionNodes.append(sn);
            i += sn.count - 1;
            break;
        }
        case 'N':
        case 'n':
        case 'E':
        case 'e':
        case 'O':
        case 'o':
        case 'D':
        case 'd': {
            SECTION day;
            if (sect == 'N' || sect == 'n') {
                day = DAY_OF_YEAR_SECTION;
            } else if (sect == 'E' || sect == 'e') {
                day = DAY_CATHOLIC_EASTER_SECTION;
            } else if (sect == 'O' || sect == 'o') {
                day = DAY_ORTHODOX_EASTER_SECTION;
            } else {
                day = DAY_OF_MONTH_SECTION;
            }

            const SectionNode sn = { day, i, countRepeat(format, i, 3), 6 };
            newSectionNodes.append(sn);
            i += sn.count - 1;
            break;
        }

        default:
            break;
        }
    }
    if (newSectionNodes.isEmpty()) {
        return false;
    }
    m_sectionNodes = newSectionNodes;

    return true;
}

int EventString::countRepeat(const QString &format, int index, int maxCount)
{
    int count = 1;
    const QChar ch(format.at(index));
    const int max = qMin(index + maxCount, format.size());
    while (index + count < max && format.at(index + count) == ch) {
        ++count;
    }
    return count;
}

bool EventString::parseOccur(const QString &occur)
{
    SECTIONS sections = NO_SECTION;
    const int sectionNodesCount = m_sectionNodes.size();

    QDate curDate = QDate::currentDate();

    int year = curDate.year();
    int year2 = 0;
    int yearInfo = 0;
    int year2Info = 0;
    int month = curDate.month();
    int week = 0;
    int day = curDate.day();
    int dayOfYear = 0;
    int dayOfEaster = 0;
    int hour = 0;
    int hour12 = 0;
    int minute = 0;
    int second = 0;
    int msecond = 0;
    int ampm = -1;

    for (int index = 0; index < sectionNodesCount; ++index) {
        int *current = 0;
        const SectionNode &sn = m_sectionNodes.at(index);

        switch (sn.type) {
        case YEAR4_INFO_SECTION:          current = &yearInfo;    break;
        case YEAR2_INFO_SECTION:          current = &year2Info;   break;
        case YEAR4_SECTION:               current = &year;        break;
        case YEAR2_SECTION:               current = &year2;       break;
        case MONTH_SECTION:               current = &month;       break;
        case WEEK_SECTION:
        case FULL_WEEK_SECTION:           current = &week;        break;
        case DAY_OF_MONTH_SECTION:        current = &day;         break;
        case DAY_OF_YEAR_SECTION:         current = &dayOfYear;   break;
        case DAY_ORTHODOX_EASTER_SECTION:
        case DAY_CATHOLIC_EASTER_SECTION: current = &dayOfEaster; break;
        case HOUR24_SECTION:              current = &hour;        break;
        case HOUR12_SECTION:              current = &hour12;      break;
        case MINUTE_SECTION:              current = &minute;      break;
        case SECOND_SECTION:              current = &second;      break;
        case MSECOND_SECTION:             current = &msecond;     break;
        case AM_SECTION:
        case PM_SECTION:                  current = &ampm;        break;
        default:
            qWarning("EventString::parse Internal error (%d)", sn.type);
            break;
        }
        QString value = QStringRef(&occur, m_sectionNodes.at(index).pos, m_sectionNodes.at(index).count).toString();
        int num = 0;
        if ((sn.type == WEEK_SECTION
            || sn.type == FULL_WEEK_SECTION)
            && (value == "L" || value == "P"))
        {
            num = int(value.at(0).toLatin1());
        } else {
            num = value.toInt();
        }

        if (!current) {
            qWarning("EventString::parse Internal error 2");
            return false;
        }
        *current = num;
        sections |= sn.type;
    }

    if (year2) {
        year = 2000 + year2;
    }

    if (year2Info) {
        yearInfo = 2000 + year2Info;
    }

    if (sections & DAY_OF_YEAR_SECTION) {
        QDate date(year, 1, 1);
        date = date.addDays(dayOfYear - 1);
        month = date.month();
        day = date.day();
    }

    if (sections & DAY_ORTHODOX_EASTER_SECTION) {
        QDate date = Event::orthodoxEasterDate(year);
        date = date.addDays(dayOfEaster);
        month = date.month();
        day = date.day();
    }

    if (sections & DAY_CATHOLIC_EASTER_SECTION) {
        QDate date = Event::catholicEasterDate(year);
        date = date.addDays(dayOfEaster);
        month = date.month();
        day = date.day();
    }

    if (sections & WEEK_SECTION || sections & FULL_WEEK_SECTION) {
        QDate date = Event::shiftEventDate(year, month, week, day, bool(sections & FULL_WEEK_SECTION));
        month = date.month();
        day = date.day();
    }

    if (hour12) {
        hour = ((sections & PM_SECTION) ? hour12 + 12 : hour12);
    }

    m_dateTime = QDateTime(QDate(year, month, day), QTime(hour, minute, second));

    return true;
}

const QDateTime &EventString::toDateTime()
{
    if (!m_dateTime.isValid()) {
        parseFormat(m_format);
        parseOccur(m_occur);
    }
    return m_dateTime;
}

EventString &EventString::normalize()
{
    if (m_occur.isEmpty() && m_format.isEmpty()) {
        return *this;
    }
    parseFormat(m_format);
    qSort(m_sectionNodes.begin(), m_sectionNodes.end(), SectionGreater());

    QString normalOccur;
    QString normalFormat;
    for (int index = 0; index < m_sectionNodes.size(); ++index) {
        QString value = QStringRef(&m_occur, m_sectionNodes.at(index).pos, m_sectionNodes.at(index).count).toString();
        QString mask = QStringRef(&m_format, m_sectionNodes.at(index).pos, m_sectionNodes.at(index).count).toString();
        normalOccur.append(value);
        normalFormat.append(mask);
    }
    if (m_occur != normalOccur) {
        m_occur = normalOccur;
    }
    if (m_format != normalFormat) {
        m_format = normalFormat;
    }
    m_dateTime = QDateTime();

    return *this;
}

QDebug operator<< (QDebug debug, const EventString &event)
{
    debug << "EventString: \n"
        << "format = " << event.format() << "\n"
        << "occur  = " << event.occur()  << "\n";
    return debug;
}
